//
//  JSONToModelVM.swift
//  ZJTools
//
//  Created by 万孟超 on 2023/10/26.
//

import Foundation

class ConvertModel {
    
    class ConvertPropertyModel {
        
        enum PropertyType: Equatable {
            case int
            case double
            case model((String?, String?))
            case string
            case modelArray((String?, String?))
            case intArray
            case stringArray
            case doubleArray
            case anyArray
            case any
            case bool
            
            func typeDes(_ prefix: String) -> String {
                switch self {
                    case .int:
                        return "Int = <#IntDefaultValue#>"
                    case .double:
                        return "Double = <#DoubleDefaultValue#>"
                    case .model((let name, _)):
                        return prefix + (name ?? "") + "?"
                    case .string:
                        return "String = \"\""
                    case .modelArray((let name, _)):
                        return "[\(prefix + (name ?? ""))] = []"
                    case .intArray:
                        return "[Int] = []"
                    case .stringArray:
                        return "[String] = []"
                    case .doubleArray:
                        return "[Double] = []"
                    case .anyArray:
                        return "[<#UnknownModel#>] = []"
                    case .any:
                        return "<#UnknownModel#>"
                    case .bool:
                        return "Bool"
                }
            }
            
            func arkTSClassTypeDes(_ prefix: String) -> String {
                switch self {
                    case .int:
                        return "number = 0"
                    case .double:
                        return "number = 0.0"
                    case .model((_, let name)):
                        return prefix + (name ?? "") + " | null"
                    case .string:
                        return "string = \"\""
                    case .modelArray((_, let name)):
                        return "\(prefix + (name ?? ""))[] = []"
                    case .intArray:
                        return "number[] = []"
                    case .stringArray:
                        return "string[] = []"
                    case .doubleArray:
                        return "number[] = []"
                    case .anyArray:
                        return "unknown[]"
                    case .any:
                        return "unknown"
                    case .bool:
                        return "boolean"
                }
            }
            
            func arkTSInterfaceTypeDes(_ prefix: String) -> String {
                switch self {
                    case .int:
                        return "number "
                    case .double:
                        return "number "
                    case .model((_, let name)):
                        return prefix + (name ?? "") + " | null"
                    case .string:
                        return "string "
                    case .modelArray((_, let name)):
                        return "\(prefix + (name ?? ""))[] "
                    case .intArray:
                        return "number[] "
                    case .stringArray:
                        return "string[] "
                    case .doubleArray:
                        return "number[] "
                    case .anyArray:
                        return "unknown[] "
                    case .any:
                        return "unknown "
                    case .bool:
                        return "boolean"
                }
            }
            
            func ocTypeDes(_ key: String, prefix: String) -> String {
                let common = "@property (nonatomic, "
                switch self {
                    case .int:
                        return common + "assign) NSInteger " + key
                    case .double:
                        return common + "assign) double " + key
                    case .model((let name, _)):
                        return common + "strong) " + prefix + (name ?? "") + " *" + key
                    case .string:
                        return common + "copy) NSString *" + key
                    case .modelArray((let name, _)):
                        return common + "copy) NSArray<\(prefix + (name ?? "")) *> *" + key
                    case .intArray:
                        return common + "copy) NSArray<NSNumber *> *" + key
                    case .stringArray:
                        return common + "copy) NSArray<NSString *> *" + key
                    case .doubleArray:
                        return common + "copy) NSArray<NSNumber *> *" + key
                    case .anyArray:
                        return common + "copy) NSArray *" + key
                    case .any:
                        return common + "strong) id " + key
                    case .bool:
                        return common + "assin) BOOL " + key
                }
            }
            
            
            private var typeIdentify: Int {
                switch self {
                    case .int:
                        return 1
                    case .double:
                        return 2
                    case .model((_,_)):
                        return 3
                    case .string:
                        return 4
                    case .modelArray((_,_)):
                        return 5
                    case .intArray:
                        return 6
                    case .stringArray:
                        return 7
                    case .doubleArray:
                        return 8
                    case .anyArray:
                        return 9
                    case .any:
                        return 0
                    case .bool:
                        return 10
                }
            }
            
            static func == (lhs: Self, rhs: Self) -> Bool {
                return lhs.typeIdentify == rhs.typeIdentify
            }
        }
        
        var name: String = ""
        var type: PropertyType = .any
        var subModelName: (String?, String?)
    }
    
    /// 类或者结构体名称
    var name: String = ""
    /// 类或者结构体名称
    var arktsName: String = ""
    
    var propertyList: [ConvertPropertyModel] = []
}



class JSONToModelVM {
    
    var contentChangeBlock: ((String) -> ())?
    
    var languageType: LanguageType = .swift {
        didSet {
            refreshConvertCode()
        }
    }
    
    var structureType: StructureType = .class {
        didSet {
            refreshConvertCode()
        }
    }
    
    var modelPrefix: String = "" {
        didSet {
            refreshConvertCode()
        }
    }
    
    var currentModelString: String = "" {
        didSet {
            Async(.main) { [self] in
                contentChangeBlock?(currentModelString)
            }
        }
    }
    
    var currentDic: [String : Any] = [:] {
        didSet {
            Async(.global()) { [self] in
                convertDicToModel()
            }
        }
    }
    
    var currentModelArray: [ConvertModel]? {
        didSet {
            refreshConvertCode()
        }
    }
    
    func convertToDic(_ json: String) throws {
        if let jsonDic = json.jsonToDic {
            currentDic = jsonDic
        }else{
            throw ConvetError.convertDetal("JSON解析失败")
        }
    }
    
    func convertDicToModel() {
        var modelArray: [ConvertModel] = []
        convertDicToSubModel(currentDic, modelArray: &modelArray)
        currentModelArray = modelArray
    }
    
    @discardableResult
    private func convertDicToSubModel(_ sub: [String : Any], StructureName: String = "", modelArray: inout [ConvertModel]) -> ConvertModel {
        let model = ConvertModel()
        let replaceString = "<#"
        model.name = replaceString + StructureName + "Name#>Model"
        model.arktsName = StructureName + "Model"
        var propertyArray: [ConvertModel.ConvertPropertyModel] = []
        for (key, value) in sub {
            let property = ConvertModel.ConvertPropertyModel()
            property.name = key
            if value is Bool  {
                property.type = .bool
            }else if value is Int  {
                property.type = .int
            }else if value is String {
                property.type = .string
            }else if value is Double || value is Float {
                property.type = .double
            }else if let value = value as? [String : Any] {
                let subModel = convertDicToSubModel(value, StructureName: key.upperFirstLetter, modelArray: &modelArray)
                property.subModelName = (subModel.name, subModel.arktsName)
                property.type = .model(property.subModelName)
            }else if let value = value as? [Any] {
                if value.isEmpty {
                    property.type = .anyArray
                }else{
                    if let valueObj = value.first as? [String : Any] {
                        let subModel = convertDicToSubModel(valueObj, StructureName: key.upperFirstLetter, modelArray: &modelArray)
                        property.subModelName = (subModel.name, subModel.arktsName)
                        property.type = .modelArray(property.subModelName)
                    }else{
                        if value.first! is Int {
                            property.type = .intArray
                        }else if value.first! is String {
                            property.type = .stringArray
                        }else if value.first! is Double {
                            property.type = .doubleArray
                        }else{
                            property.type = .anyArray
                        }
                    }
                }
            }else{
                property.type = .any
            }
            propertyArray.append(property)
        }
        model.propertyList = propertyArray
        let tuple = deWeightModel(model, modelArray: modelArray)
        if !tuple.0 {
            modelArray.append(model)
        }
        return tuple.1
    }
    
    func convertModelToCode() throws -> String {
        guard let currentModelArray = currentModelArray else {
            throw ConvetError.convertDetal("没有Model数据")
        }
        if languageType == .swift {
            var classArray: [String] = []
            let structureStr = structureType == .class ? "class xxx: NSObject {\n\n" : "struct xxx {\n\n"
            for structureModel in currentModelArray {
                var classContent = structureStr.replacingOccurrences(of: "xxx", with: modelPrefix + structureModel.name)
                for property in structureModel.propertyList {
                    classContent += "       var " + property.name + ": " + property.type.typeDes(modelPrefix) + "\n\n"
                }
                classContent += "}"
                classArray.append(classContent)
            }
            return "import Foundation\n\n" + classArray.joined(separator: "\n\n")
        }else if languageType == .arkTS {
            var classArray: [String] = []
            let structureStr = structureType == .class ? "class xxx {\n\n" : "interface xxx {\n\n"
            for structureModel in currentModelArray {
                var classContent = structureStr.replacingOccurrences(of: "xxx", with: modelPrefix + structureModel.arktsName)
                for property in structureModel.propertyList {
                    let typeString = structureType == .class ? property.type.arkTSClassTypeDes(modelPrefix) : property.type.arkTSInterfaceTypeDes(modelPrefix)
                    classContent += "      " + property.name + ": " + typeString + "\n\n"
                }
                classContent += "}"
                classArray.append(classContent)
            }
            return classArray.joined(separator: "\n\n")
        }else{
            var classArray: [String] = []
            let structureStr = "@interface xxx : NSObject \n\n"
            for structureModel in currentModelArray {
                var classContent = structureStr.replacingOccurrences(of: "xxx", with: modelPrefix + structureModel.name)
                for property in structureModel.propertyList {
                    classContent += property.type.ocTypeDes(property.name, prefix: modelPrefix) + ";" + "\n\n"
                }
                classContent += "@end" + "\n\n"
                classContent += "@implementation " + modelPrefix + structureModel.name + "\n\n"
                classContent += "@end"
                classArray.append(classContent)
            }
            return "#import <Foundation/Foundation.h>\n\n" + classArray.reversed().joined(separator: "\n\n\n")
        }
    }
    
    func refreshConvertCode() {
        Async(.global()) { [self] in
            do {
                currentModelString = try convertModelToCode()
            }catch { }
        }
    }
    
    func deWeightModel(_ model: ConvertModel, modelArray: [ConvertModel]) -> (Bool, ConvertModel) {
        for lastModel in modelArray {
            let moreModel = lastModel.propertyList.count > model.propertyList.count ? lastModel : model
            let lessModel = lastModel.propertyList.count <= model.propertyList.count ? lastModel : model
            var isSame: Bool = true
            for propertyOne in moreModel.propertyList {
                for propertyTwo in lessModel.propertyList {
                    if propertyTwo.name != propertyOne.name || propertyTwo.type != propertyOne.type {
                        isSame = false
                        break
                    }
                }
                if !isSame { break }
            }
            if isSame {
                return (true, lastModel)
            }
        }
        return (false, model)
    }
}

extension JSONToModelVM {
    func obtainPropertyNameRang(_ content: String) throws -> [NSRange] {
        let pattern = "var\\s(.*?)\\:"

        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let nsString = NSString(string: content)
            
            let results = regex.matches(in: content, options: [], range: NSRange(location: 0, length: nsString.length))
            
            var rangs: [NSRange] = []
            for result in results {
                let range = result.range(at: 1)
                rangs.append(range)
            }
            return rangs
        } catch {
            throw ConvetError.convertDetal("")
        }
    }
    
    func obtainArkTSPropertyNameRang(_ content: String) throws -> [NSRange] {
        let pattern = "\\s(.*?)\\:"

        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let nsString = NSString(string: content)
            
            let results = regex.matches(in: content, options: [], range: NSRange(location: 0, length: nsString.length))
            
            var rangs: [NSRange] = []
            for result in results {
                let range = result.range(at: 1)
                rangs.append(range)
            }
            return rangs
        } catch {
            throw ConvetError.convertDetal("")
        }
    }
    
    func obtainOCPropertyNameRang(_ content: String) throws -> [NSRange] {
        let pattern = "@property\\s(.*?)\\)"

        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let nsString = NSString(string: content)
            
            let results = regex.matches(in: content, options: [], range: NSRange(location: 0, length: nsString.length))
            
            var rangs: [NSRange] = []
            for result in results {
                rangs.append(result.range)
            }
            return rangs
        } catch {
            throw ConvetError.convertDetal("")
        }
    }
}

extension JSONToModelVM {
    enum ConvetError: Error {
        case convertDetal(String)
    }
    
    enum LanguageType: Int {
        case swift
        case oc
        case arkTS
    }
    
    enum StructureType: Int {
        case `class`
        case `structOrInterface`
    }
}
